package it.netsphere.nswing.event;

import it.netsphere.nswing.annotation.NSEventGetParamMethod;
import it.netsphere.nswing.annotation.NSEventMethod;
import it.netsphere.nswing.annotation.NSEventPermissionMethod;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang3.StringUtils;

/**
 * 
 * @author alberto.goldoni
 * 
 */

public class NSEventManager {

    private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(NSEventManager.class);

    Set<Object> listeners = new HashSet<Object>();

    private NSEventManager() {
    }

    private static NSEventManager instance;

    public static NSEventManager getInstance() {
        if (instance == null)
            instance = new NSEventManager();
        return instance;
    }

    public void addUIListener(Object l) {
        listeners.add(l);
    }

    public void removeUIListener(Object l) {
        listeners.remove(l);
    }

    public static void dispatchUIEvent(String eventCode, Object param) {
        dispatchUIEvent(new NSEvent(eventCode, param));
    }

    public static void dispatchUIEvent(NSEvent event) {
        dispatch(event);
    }

    public static void dispatch(final NSEvent event) {
        getExecutorService().submit(new Runnable() {

            @Override
            public void run() {
                getInstance().dispatchInner(event);
            }
        });
    }

    private void dispatchInner(NSEvent event) {
        if (LOG.isDebugEnabled())
            LOG.debug(String.format("dispatching event: %s", event));

        // if(event.getEvtCode()==UIEventCode.PUL_GET_USERS)
        // LOG.debug("aaaa");

        if (listeners == null || listeners.size() == 0)
            return;

        List<Object> l = new ArrayList<Object>(listeners);

        List<NSEvent> errorEvents = new ArrayList<NSEvent>();

        for (Object listener : l) {
            Method m = getMethod4Event(listener.getClass(), event);
            if (m != null) {

                try {
                    if (LOG.isDebugEnabled())
                        LOG.debug(String.format("invoking handler method %s for event %s", m.getName(), event));
                    if (m.getParameterTypes().length == 0)
                        m.invoke(listener, new Object[0]);
                    else
                        m.invoke(listener, new Object[] { event });
                    if (LOG.isDebugEnabled())
                        LOG.debug(String.format("invoked handler method %s", m.getName()));
                } catch (InvocationTargetException e) {
                    LOG.error(e.getCause().getMessage(), e.getCause());
                    errorEvents.add(event.createError((Exception) e.getCause()));
                } catch (Exception e) {
                    LOG.error(e.getMessage(), e);
                    errorEvents.add(event.createError((Exception) e.getCause()));
                }

            }

        }

        // dispatching errors

        boolean foundOneListenerForError = false;
        for (NSEvent errorEvent : errorEvents) {
            for (Object listener : l) {
                Method m = getMethod4Event(listener.getClass(), errorEvent);
                if (m != null) {
                    foundOneListenerForError = true;
                    try {
                        if (LOG.isDebugEnabled())
                            LOG.debug(String.format("invoking handler method %s for error event %s", m.getName(), errorEvent));
                        if (m.getParameterTypes().length == 0)
                            m.invoke(listener, new Object[0]);
                        else
                            m.invoke(listener, new Object[] { errorEvent });
                        if (LOG.isDebugEnabled())
                            LOG.debug(String.format("invoked handler method %s", m.getName()));
                    } catch (InvocationTargetException e) {
                        LOG.error(e.getCause().getMessage(), e.getCause());
                    } catch (Exception e) {
                        LOG.error(e.getMessage(), e);
                    }

                }

            }

        }

        // an error but not handlers found: try to find a default handler "ANY"
        if (errorEvents.size() > 0 && foundOneListenerForError == false) {
            for (Object listener : l) {
                Method m = getMethod4EventErrorAny(listener.getClass());
                if (m != null) {
                    NSEvent erroEvent = errorEvents.get(0);

                    try {
                        if (LOG.isDebugEnabled())
                            LOG.debug(String.format("invoking handler method %s for event %s", m.getName(), erroEvent));
                        if (m.getParameterTypes().length == 0)
                            m.invoke(listener, new Object[0]);
                        else
                            m.invoke(listener, new Object[] { erroEvent });
                        if (LOG.isDebugEnabled())
                            LOG.debug(String.format("invoked handler method %s", m.getName()));
                    } catch (InvocationTargetException e) {
                        LOG.error(e.getCause().getMessage(), e.getCause());
                        errorEvents.add(event.createError((Exception) e.getCause()));
                    } catch (Exception e) {
                        LOG.error(e.getMessage(), e);
                        errorEvents.add(event.createError((Exception) e.getCause()));
                    }

                }

            }

        }

    }

    private static ExecutorService executorService;

    private static ExecutorService getExecutorService() {
        if (executorService == null) {
            executorService = Executors.newFixedThreadPool(10);
        }

        return executorService;
    }

    public static void fireUIEvent(Object o, String eventCode) {
        if (o == null)
            return;

        Class<?> cl = o.getClass();

        Method mPermission = getMethodPermission4Event(cl, eventCode);
        boolean goOn = true;

        // must proceed?
        if (mPermission != null) {
            try {
                goOn = (Boolean) mPermission.invoke(o, new Object[0]);
            } catch (IllegalAccessException e) {
                LOG.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage(), e);
            } catch (InvocationTargetException e) {
                LOG.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage(), e);
            }
        }

        if (goOn == false)
            return;

        Object param = null;
        // load a param if present
        Method mGetParam = getMethodGetParam4Event(cl, eventCode);
        if (mGetParam != null) {
            try {
                param = mGetParam.invoke(o, new Object[0]);
            } catch (IllegalAccessException e) {
                LOG.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage(), e);
            } catch (InvocationTargetException e) {
                LOG.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage(), e);
            }
        }

        NSEvent ev = new NSEvent(eventCode, param);
        dispatchUIEvent(ev);

    }

    private static Method getMethodPermission4Event(Class<?> cl, final String code) {

        for (Method m : cl.getDeclaredMethods()) {

            if (m.isAnnotationPresent(NSEventPermissionMethod.class)) {
                NSEventPermissionMethod im = m.getAnnotation(NSEventPermissionMethod.class);
                if (im.code() == code) {
                    return m;
                }
            }
        }

        return null;
    }

    private static Method getMethodGetParam4Event(Class<?> cl, final String code) {

        for (Method m : cl.getDeclaredMethods()) {

            if (m.isAnnotationPresent(NSEventGetParamMethod.class)) {
                NSEventGetParamMethod im = m.getAnnotation(NSEventGetParamMethod.class);
                if (im.code() == code) {
                    return m;
                }
            }
        }

        return null;
    }

    private static Method getMethod4Event(Class<?> cl, final NSEvent event) {

        for (Method m : cl.getMethods()) {

            if (m.isAnnotationPresent(NSEventMethod.class)) {
                NSEventMethod im = m.getAnnotation(NSEventMethod.class);
                if (StringUtils.equals(im.code(), event.getEvtCode()) && im.mode() == event.getMode()) {
                    return m;
                }

            }
        }

        // default error handling
        if (event.getMode() == NSEventMode.ERROR) {
            for (Method m : cl.getMethods()) {

                if (m.isAnnotationPresent(NSEventMethod.class)) {
                    NSEventMethod im = m.getAnnotation(NSEventMethod.class);
                    if (im.code() == event.getEvtCode() && im.mode() == event.getMode()) {
                        return m;
                    }

                }
            }

            /*
             * for( Method m : cl.getMethods() ) {
             * 
             * if(m.isAnnotationPresent( UIEventMethod.class )) { UIEventMethod im = m.getAnnotation( UIEventMethod.class ); if( im.code()==UIEventCode.ANY && im.mode()==event.getMode() ) { return m;
             * }
             * 
             * } }
             */
        }

        return null;
    }

    private static Method getMethod4EventErrorAny(Class<?> cl) {
        {

            for (Method m : cl.getMethods()) {

                if (m.isAnnotationPresent(NSEventMethod.class)) {
                    NSEventMethod im = m.getAnnotation(NSEventMethod.class);
                    if (StringUtils.equalsIgnoreCase(im.code(), "any") && im.mode() == NSEventMode.ERROR) {
                        return m;
                    }

                }
            }
        }

        return null;
    }

}
